﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace M3G {
    public class AnimationController : Object3D {

        #region Field
        int   activeIntervalStart;
        int   activeIntervalEnd;
        float blendingWeight;
        float speed;
        float sequencialReferencePoint;
        int   worldReferencePoint;
        #endregion



        #region Method
        public AnimationController () {
            this.activeIntervalEnd = 0;
            this.activeIntervalStart = 0;
            this.blendingWeight = 1.0f;
            this.speed = 1.0f;
            this.sequencialReferencePoint = 0;
            this.worldReferencePoint = 0;
        }

        protected AnimationController (AnimationController animationController) :
            base (animationController) {
            this.activeIntervalStart = animationController.activeIntervalStart;
            this.activeIntervalEnd = animationController.activeIntervalEnd;
            this.blendingWeight = animationController.blendingWeight;
            this.speed = animationController.speed;
            this.sequencialReferencePoint = animationController.sequencialReferencePoint;
            this.worldReferencePoint = animationController.worldReferencePoint;
        }


        // M3G非標準だがいちいちActiveIntervalStart()/End()を呼んで判定するのはめんどくさいので。
        public bool Active (int worldTime) {
            if (activeIntervalStart == 0 && activeIntervalEnd == 0) {
                return true;
            }
            if (worldTime < activeIntervalStart || worldTime > activeIntervalEnd) {
                return false;
            }

            return true;
        }


        public int ActiveIntervalStart {
            get { return activeIntervalStart; }
        }

        public int ActiveIntervalEnd {
            get { return activeIntervalEnd; }
        }


        public void SetActiveInterval (int start, int end) {
            if (start > end) {
                throw new ArgumentException ("Active interval must be start<end, s=" + start + ",e=" + end);
            }

            this.activeIntervalStart = start;
            this.activeIntervalEnd = end;
        }

        public float Speed {
            get { return speed; }
        }

        public void SetSpeed (float speed, int worldTime) {

            this.sequencialReferencePoint = Position (worldTime);
            this.worldReferencePoint = worldTime;
            this.speed = speed;
        }

        public float Position (int worldTime) {
            return sequencialReferencePoint + speed * (worldTime - worldReferencePoint);
        }

        public void SetPosition (float sequenceTime, int worldTime) {

            this.sequencialReferencePoint = sequenceTime;
            this.worldReferencePoint = worldTime;
        }

        public float Weight {
            get { return blendingWeight; }
            set { blendingWeight = value; }
        }



        public int WorldTime (float sequenceTime) {
            if (speed == 0) {
                throw new InvalidProgramException ("Speed == 0 leads arithmatic error");
            }

            return (int)((sequenceTime - sequencialReferencePoint) / speed + worldReferencePoint);
        }

        public int Duration {
            get {
                if (speed == 0) {
                    throw new InvalidProgramException ("Speed == 0 leads arithmatic error");
                }

                var duration = 0;
                foreach (var track in AnimationTrack) {
                    var d = track.KeyframeSequence.Duration;
                    if (d > duration) {
                        duration = d;
                    }
                }

                return (int)(duration / speed);
            }
        }

        public override int Animate (int time) {

            base.Animate (time);

            var animatedAnimationPosition = new float[2];
            var animatedAnimationSpeed    = new float[2];
            var animatedAnimationWeight   = new float[1];
            var value                     = new float[2];
            var animated                  = new Dictionary<int, bool> (3);

            for (var i=0; i < AnimationTrackCount; i++) {
                var track = AnimationTrack[i];
                var ch    = AnimationTrackChannel[i];
                if (track.Controller == null || !track.Controller.Active (time)) {
                    continue;
                }
                var property = track.TargetProperty;
                var weight   = track.Controller.Weight;

                animated[property] = true;
                switch (property) {
                    case M3G.AnimationTrack.AnimationPosition: {
                        track.Sample (time, ch, value);
                        if (track.KeyframeSequence.ComponentCount == 1) {
                            value[1] = time;
                        }
                        animatedAnimationPosition[0] = value[0] * weight;  // Sequence Time
                        animatedAnimationPosition[1] = value[1] * weight;  // World Time
                        break;
                    }
                    case M3G.AnimationTrack.AnimationSpeed: {
                        track.Sample (time, ch, value);
                        if (track.KeyframeSequence.ComponentCount == 1) {
                            value[1] = time;
                        }
                        animatedAnimationSpeed[0] = value[0] * weight;  // Speed
                        animatedAnimationSpeed[1] = value[1] * weight;  // World Time
                        break;
                    }
                    case M3G.AnimationTrack.AnimationWeight: {
                        track.Sample (time, ch, value);
                        animatedAnimationWeight[0] = value[0] * weight;
                        break;
                    }
                }// switch
            }  // for

            if (animated[M3G.AnimationTrack.AnimationPosition]) {
                this.sequencialReferencePoint = animatedAnimationPosition[0];
                this.worldReferencePoint = (int)animatedAnimationPosition[1];
            }
            if (animated[M3G.AnimationTrack.AnimationSpeed]) {
                this.sequencialReferencePoint = Position ((int)animatedAnimationSpeed[1]);
                this.worldReferencePoint = (int)animatedAnimationSpeed[1];
                this.speed = animatedAnimationSpeed[0];
            }
            if (animated[M3G.AnimationTrack.AnimationWeight]) {
                this.blendingWeight = animatedAnimationWeight[0];
            }

            return 0;

        }

        public override Object3D Duplicate () {
            return new AnimationController (this);
        }
        #endregion
    }
}
